package com.myminio.springbootminio.service

import cn.hutool.core.io.FileUtil
import cn.hutool.json.JSONObject
import com.myminio.springbootminio.entity.RestResult
import io.minio.*
import io.minio.messages.Bucket
import lombok.SneakyThrows
import mu.KotlinLogging
import org.apache.commons.lang3.StringUtils
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Service
import org.springframework.web.context.request.RequestContextHolder
import org.springframework.web.context.request.ServletRequestAttributes
import org.springframework.web.multipart.MultipartFile
import org.springframework.web.multipart.commons.CommonsMultipartFile
import java.io.*
import java.net.URLEncoder
import java.time.format.DateTimeFormatter
import java.util.*
import javax.annotation.Resource

@Service
class MinIoService {
    @Resource
    private lateinit var    minioClient: MinioClient

    private val log = KotlinLogging.logger(this.javaClass.name)

    @Value("\${spring.servlet.multipart.location}")
    private lateinit var mulFilePath:String

    @SneakyThrows
    fun listBuckets(): List<Bucket>{
        return minioClient.listBuckets()
    }
    @SneakyThrows
    fun getAllBucket(listObj:Boolean):RestResult<JSONObject>{
        val buckets = listBuckets()
        val json = JSONObject()
        val list = mutableListOf<Set<Map<String,Any>>>()
        json["size"] = buckets.size
        buckets.forEach {
            val set =listAllObjsByBucket(it.name(),true)
            if(set.isNotEmpty()){list.add(set)}
        }
        json["list"] = list
        return RestResult(true,200,"请求成功",json)
    }
    private fun listAllObjsByBucket(bucketName: String,untilEnd:Boolean):Set<Map<String,Any>>{
        var realBucketName = bucketName
        var childDir = ""
        if(bucketName.contains("/")){
            realBucketName= bucketName.split("/")[0]
            childDir=bucketName.replace("$realBucketName/","")
        }
        val builder = if(childDir.isNotEmpty()) ListObjectsArgs.builder().bucket(realBucketName).prefix(childDir).recursive(false).build() else ListObjectsArgs.builder().bucket(bucketName).build()
        val iterable = minioClient.listObjects(builder)
        val list = iterable.toList()
        val set = mutableSetOf<Map<String,Any>>()
        if(list.isNotEmpty()){
            list.forEach { result->
                val item = result.get()
                val objMap = mutableMapOf<String,String>()
                if(item.isDir){
                    if(untilEnd && !item.objectName().equals(bucketName)) {
                        val childBucketName = bucketName+"/"+item.objectName()
                        set.addAll(listAllObjsByBucket(childBucketName , true))
                    }else{
                        objMap["fileType"]="目录"
                        objMap["fileName"] = item.objectName()
                        set.add(objMap)
                    }
                }else{
                    objMap["size"] = item.size().toString()
                    objMap["fileType"]="文件"
                    objMap["owner"] = item.owner().displayName()
                    val metaData = item.userMetadata()
                    if (metaData != null && metaData.isNotEmpty()) metaData.forEach { (k, v) -> objMap[k] = v }
                    val upTime = item.lastModified()
                    objMap["lastUpdate"] = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(upTime.toLocalDateTime())
                    if(item.objectName().contains("/")){
                        val names = item.objectName().split("/")
                        objMap["fileName"] = names[names.size-1]
                    }else{
                        objMap["fileName"]=item.objectName()
                    }
                    objMap["dirName"]=bucketName
                    set.add(objMap)
                }

            }

        }
        return set
    }
    @SneakyThrows
    fun listBucketNames():List<String> {
        val bucketNames:MutableList<String> = mutableListOf()
        val buckets = listBuckets()
        buckets.forEach { bucketNames.add(it.name()) }
        return bucketNames
    }
    fun bucketExists(bucketName:String):Boolean{
        return listBucketNames().contains(bucketName)
    }

    /**
     * 创建桶
     */
    fun createBucket(bucketName:String):Boolean{
        if (bucketExists(bucketName)){
           log.info("Failed to create bucket, $bucketName 已存在")
            return false
        }
        return try {
            minioClient.makeBucket(MakeBucketArgs.builder().objectLock(true).bucket(bucketName).build())
            true
        }catch (e:Exception){
            log.error("發生異常",e)
            false
        }
    }

    /**
     * 删除
     */
    fun deleteBucket(bucketName:String):Boolean{
        return try {
            minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build())
            true
        }catch (e:Exception){
            log.error("删除bucket {}失败,原因:",bucketName,e)
            false
        }
    }
    fun commonUploadFile(mulFile:MultipartFile):RestResult<String>{
        val startTime = System.currentTimeMillis()
        val file = File(mulFilePath+mulFile.originalFilename)
        /*when{
            !FileUtil.exist(filePath)->File(filePath).mkdir()
            !FileUtil.exist(filePath+mulFile.originalFilename)->
            {

                val flag = file.createNewFile()
                log.info("创建文件是否成功？{}",if(flag) "是" else "否")
            }
            else->{
                log.info("文件${mulFile.originalFilename}已存在")
            }
        }*/
        mulFile.transferTo(file)
        val endTime = System.currentTimeMillis()
        log.info("transferTo上传文件耗时:${endTime-startTime} 毫秒")
        return RestResult(true,200,"使用transferTo()方法上传文件成功")
    }
    fun uploadByInputStream(mulFile:MultipartFile):RestResult<String>{
        val startTime = System.currentTimeMillis()
        val filePath = mulFilePath+mulFile.originalFilename
        val file =  File(filePath)
        /*if(!file.exists()){
            val flag = file.createNewFile()
            if(!flag){
                return RestResult(false,500,"创建文件${mulFile.originalFilename}失败")
            }
        }*/
        val out = FileOutputStream(file)
        val buf = ByteArray(1024)
        val inputStream = BufferedInputStream(mulFile.inputStream)
        var len:Int
        try {
            while (((inputStream.read(buf)).also { len=it })!= -1){
                out.write(buf,0,len)
            }
            return  RestResult(true,200,"上传文件${mulFile.originalFilename}成功")
        }catch (e:Exception){
            val resStr = "上传文件${mulFile.originalFilename}失败"
            log.error(resStr)
            e.printStackTrace()
            return RestResult(500,resStr)
        }finally {
            out.close()
            inputStream.close()
            val endTime = System.currentTimeMillis()
            log.info("inputStream上传文件耗时:${endTime-startTime} 毫秒")
        }

    }
    fun uploadFile(file:MultipartFile):RestResult<String> {
        val startTime = System.currentTimeMillis()
        val result = RestResult<String>()
        val req = (RequestContextHolder.currentRequestAttributes() as ServletRequestAttributes).request
        val bucketName = req.getParameter("bucketName")
        result.success = false
        when {
            file.size == 0L -> {
                result.code = 401
                result.msg = "上传文件不能为空"
            }
            file.originalFilename!!.lowercase(Locale.getDefault()).endsWith(".exe") -> {
                result.code = 500
                result.msg = "请压缩exe文件成rar/zip/7z文件后再上传"
            }
            StringUtils.isBlank(bucketName) -> {
                result.code = 500
                result.msg = "请指定存储对象名称bucketName"
            }
            else -> {
                val inputStream = file.inputStream
                val contentType = file.contentType
                if (!bucketExists(bucketName)) {
                    if (!createBucket(bucketName)) {
                        inputStream.close()
                        return RestResult(false, 500, "创建存储对象空间失败，无法上传文件")
                    }
                }
                val res=minioClient.putObject(PutObjectArgs.builder()
                    .bucket(bucketName)
                    .contentType(contentType)
                    .stream(inputStream,file.size,1024*1024*5)
                    .`object`(file.originalFilename)
                    .build()
                )
                result.success = true
                result.msg="upload successfully"
                result.data=res.toString()
                inputStream.close()
            }
        }
        val endTime = System.currentTimeMillis()
        log.info("minio上传文件耗时:${endTime-startTime} 毫秒")
        return result
    }

    fun downloadFile(bucketName: String,fileName:String):RestResult<String>{
        var inputStream: InputStream? =null
        var outputStream: OutputStream? =null
        val res = (RequestContextHolder.currentRequestAttributes() as ServletRequestAttributes).response
        try{
            inputStream=minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).`object`(fileName).build())
            res!!.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"))
            res.contentType = "application/octet-stream"
            res.characterEncoding = "utf-8"
            outputStream=res.outputStream
            val buf = ByteArray(1024)
            while(inputStream.read(buf)>0){
                outputStream.write(buf,0,inputStream.read(buf))
            }
            return RestResult(true,200,"下载文件成功")

        }catch (e:Exception){
            log.error("下载文件发生异常:",e)
            return RestResult(false,500,"下载文件失败")
        }finally {
            inputStream?.close()
            outputStream?.close()
        }
    }
    fun deleteFile(bucketName: String,fileName: String): RestResult<String> {
        return try{
            val res = minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).`object`(fileName) .build())
            log.info("删除成功，返回消息:{}",res.toString())
            RestResult(true,200,"删除文件成功")
        }catch (e:Exception){
            log.error("删除文件时发生异常",e)
            RestResult(false,500,"删除文件失败")
        }
    }
}